/**
 *
 * 单选，框选，多边形选择
 * @param      viewer                   Mars3d实例对象
 * @param      highlight                是否选中高亮（启用着色器）
 * @param      isClearShaderOnMultiple  选择后是否清空之前的着色
 * @param      isClearShaderOnStop      停止后是否清空之前的着色
 * 
 * @function   singleSelectTool         单选
 * @function   boxSelectTool            框选
 * @function   rectSelectTool           多边形选择
 * @function   stopBoxSelectTool        停止框选
 * @function   stopSingleSelectTool     停止单选
 * @function   stopRectSelectTool       停止多边形选择
 * @function   clearShader              清空着色器
 * @function   clearRect                清空多边形
 *
 */


let startPosition = new Cesium.Cartesian2()
let endPosition = new Cesium.Cartesian2()

const fragmentShader = `
uniform sampler2D colorTexture; 
varying vec2 v_textureCoordinates; 
uniform vec2 startPosition;
uniform vec2 endPosition;
void main(void){
  float minX = startPosition.x < endPosition.x ? startPosition.x : endPosition.x;
  float minY = startPosition.y < endPosition.y ? startPosition.y : endPosition.y;
  float maxX = startPosition.x > endPosition.x ? startPosition.x : endPosition.x;
  float maxY = startPosition.y > endPosition.y ? startPosition.y : endPosition.y;
  if( gl_FragCoord.x >= minX && gl_FragCoord.x <= maxX && gl_FragCoord.y >= minY && gl_FragCoord.y <= maxY){
    gl_FragColor = mix(texture2D(colorTexture, v_textureCoordinates), vec4(0.0,1.0,0.0,1.0), 0.2);
  }
  else{
    gl_FragColor = texture2D(colorTexture, v_textureCoordinates);
  }
}`


const selectMap = new Map()
export function BQGIS_initSelectTool(option) {
    const selectOption = Object.assign({ viewer: window.viewer, highlight: true, isClearShaderOnMultiple: false, isClearShaderOnStop: false }, option)
    if(selectMap.has(selectOption.viewer)){
        const instance = selectMap.get(viewer)
        instance.shaderOption = { highlight: selectOption.highlight, isClearShaderOnMultiple: selectOption.isClearShaderOnMultiple, isClearShaderOnStop: selectOption.isClearShaderOnStop }
        return instance
    }else{
        const instance = new BQGIS_selectTool(selectOption)
        selectMap.set(viewer, instance)
        return instance
    }
}

function BQGIS_selectTool({viewer, highlight, isClearShaderOnMultiple, isClearShaderOnStop}) {
    this.viewer = viewer
    this.scene = this.viewer.scene
    this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    this.messageHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    this.graphicLayer = new mars3d.layer.GraphicLayer()
    this.shaderOption = { highlight, isClearShaderOnMultiple, isClearShaderOnStop }
    this.message = ''
    this.viewer.addLayer(this.graphicLayer)
    this.initPostProcess()
}


BQGIS_selectTool.prototype.closeSmallTooltip = function () {
    this.messageHandler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
    this.viewer.closeSmallTooltip()
}


BQGIS_selectTool.prototype.initPostProcess = function () {
    this.yellowEdge = Cesium.PostProcessStageLibrary.createEdgeDetectionStage();
    this.yellowEdge.selected = [];
    this.yellowEdge.uniforms.length = 0.01;
    this.yellowEdge.uniforms.color = Cesium.Color.YELLOW;
    viewer.scene.postProcessStages.add(Cesium.PostProcessStageLibrary.createSilhouetteStage([this.yellowEdge]))
}


/**
 *
 * 单选
 * @param   onSelect                 回调函数，左键选中模型时触发，返回当前选中的模型
 * @param   onCancel                 回调函数，右键退出时触发
 *
 */
BQGIS_selectTool.prototype.singleSelectTool = function (option) {
    this.stopBoxSelectTool()
    this.stopRectSelectTool()
    const selectOption = Object.assign({ onSelect: () => {}, onCancel: () => {} }, option)
    const { onSelect, onCancel } = selectOption
    this.messageHandler.setInputAction((event) => {
        this.viewer.openSmallTooltip(event.endPosition, this.message)
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
    this.message = '左键选中模型,右键退出选择'
    this.handler.setInputAction((movement) => {
        const feature = this.scene.pick(movement.position)
        if (!Cesium.defined(feature)) {
            return
        }
        const bool = this.viewer.scene.globe.depthTestAgainstTerrain
        this.viewer.scene.globe.depthTestAgainstTerrain = true;
        let earthPosition = viewer.scene.pickPosition(movement.position)
        this.viewer.scene.globe.depthTestAgainstTerrain = bool
        if(this.shaderOption.isClearShaderOnMultiple){
            this.yellowEdge.selected = []
        }
        const selectData = this.format(feature, this.shaderOption.highlight)
        if(selectData){
            onSelect(selectData, earthPosition)
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
    this.handler.setInputAction((movement) => {
        this.stopSingleSelectTool()
        onCancel()
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
}

BQGIS_selectTool.prototype.stopSingleSelectTool = function () {
    if(this.shaderOption.isClearShaderOnStop){
        this.yellowEdge.selected = []
    }
    this.closeSmallTooltip()
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK)
}

/**
 *
 * 框选
 * @param   onSelect                 回调函数，框选完成时触发，返回当前选中的模型
 * @param   onCancel                 回调函数，右键退出时触发
 *
 */
BQGIS_selectTool.prototype.boxSelectTool = function (option) {
    this.stopSingleSelectTool()
    this.stopRectSelectTool()
    const selectOption = Object.assign({ onSelect: () => {}, onCancel: () => {} }, option)
    const { onSelect, onCancel } = selectOption
    const mainCanvas = this.viewer.canvas
    this.messageHandler.setInputAction((event) => {
        this.viewer.openSmallTooltip(event.endPosition, this.message)
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
    this.message = '按住左键拖拽选择模型,单击右键退出选择'
    this.viewer.viewer.scene.screenSpaceCameraController.enableRotate = false
    this.viewer.viewer.scene.screenSpaceCameraController.enableTranslate = false
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
    this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    viewer.scene.postProcessStages.add(new Cesium.PostProcessStage({
        fragmentShader,
        uniforms: {
            startPosition: function () {
                return new Cesium.Cartesian2(startPosition.x, mainCanvas.height - startPosition.y)
            },
            endPosition: function () {
                return new Cesium.Cartesian2(endPosition.x, mainCanvas.height - endPosition.y)
            },
        }
    }))

    this.handler.setInputAction(({
        position
    }) => {
        startPosition = position
        endPosition = position
        this.handler.setInputAction((movement) => {
            endPosition = movement.endPosition
        }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
    }, Cesium.ScreenSpaceEventType.LEFT_DOWN)

    this.handler.setInputAction(({
        position
    }) => {
        this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
        endPosition = position
        const width = Math.abs(endPosition.x - startPosition.x)
        const height = Math.abs(endPosition.y - startPosition.y)
        const center = new Cesium.Cartesian2((endPosition.x + startPosition.x) / 2, (endPosition.y + startPosition.y) / 2)
        const arr = this.viewer.scene.drillPick(center, null, width, height)
        const selectData = []
        if(this.shaderOption.isClearShaderOnMultiple){
            this.yellowEdge.selected = []
        }
        arr.forEach(item => {
           const e = this.format(item)
           if(e){
            selectData.push(e)
           }
        })
        onSelect(selectData)
        startPosition = new Cesium.Cartesian2()
        endPosition = new Cesium.Cartesian2()
    }, Cesium.ScreenSpaceEventType.LEFT_UP);

    this.handler.setInputAction((movement) => {
        this.stopBoxSelectTool()
        onCancel()
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
}

BQGIS_selectTool.prototype.stopBoxSelectTool = function () {
    if(this.shaderOption.isClearShaderOnStop){
        this.yellowEdge.selected = []
    }
    startPosition = new Cesium.Cartesian2()
    endPosition = new Cesium.Cartesian2()
    this.viewer.viewer.scene.screenSpaceCameraController.enableRotate = true
    this.viewer.viewer.scene.screenSpaceCameraController.enableTranslate = true
    this.closeSmallTooltip()
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
    this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
}

/**
 *
 * 多边形选择
 * 
 * @param   onSelect     回调函数，选择完成时触发，返回当前选中的模型
 * @param   type         option.type === 3dtiles 时选择3dtiles，否则选择矢量
 * 
 * 
 * 说明
 * 本模块使用两种方式实现该功能，使用射线求交的方式可以选取 3dtiles 模型，但是无法选择矢量，而使用遍历求交的方式，可以选择矢量，但无法选择 3dtiles，相对于其他模块多了一个type参数来区分两种选择方式
 * 
 * 选取 3dtiles 本质上是使用了密集的射线求交进行模拟，性能较差,而且可能漏掉一些体积较小的模型
 * 
 * 选取矢量的方式，是通过遍历所有加载的entity(viewer.entities),所以只能查找出通过entity方式加载的矢量，另外无法通过后处理来显示高光效果，可以在拿到返回的entity后，手动更改entity的属性来区分选中项(详情可参考示例)
 * 
 */
BQGIS_selectTool.prototype.rectSelectTool = function (option) {
    this.stopBoxSelectTool()
    this.stopSingleSelectTool()
    const selectOption = Object.assign({ onSelect: () => {}, type: 'billboard', highlight: true}, option)
    const { onSelect, type } = selectOption
    this.messageHandler.setInputAction((event) => {
        this.viewer.openSmallTooltip(event.endPosition, this.message)
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
    this.graphicLayer.isContinued = true
    this.graphicLayer.startDraw({
        type: "polygon",
        style: {
            color: "#ffff00",
            opacity: 0.2,
            clampToGround: true
        },
        success: (graphic) => {
            if(this.shaderOption.isClearShaderOnMultiple){
                this.yellowEdge.selected = []
            }
            this.closeSmallTooltip()
            if(type === '3dtiles'){
                const tilesArr = this.selected3DtileByRay(graphic)
                onSelect(tilesArr.map(item => this.format(item)))
            }else {
                const entities = this.selectedEntities(graphic)
                onSelect(entities)
            }
        }
    })
}

BQGIS_selectTool.prototype.stopRectSelectTool = function () {
    if(this.shaderOption.isClearShaderOnStop){
        this.yellowEdge.selected = []
    }
    this.graphicLayer.isContinued = false
    this.graphicLayer.stopDraw()
    this.closeSmallTooltip()
}

BQGIS_selectTool.prototype.selectedEntities = function (graphic) {
    const selected = []
    this.viewer.entities.values.forEach(element => {
        const position = element.position.getValue()
        const isInArea = graphic.isInPoly(position)
        if (isInArea) {
          selected.push({ 
            id: element.id,
            name: element.name,
            graphic: element
          })
        }
      });
    return selected
}

BQGIS_selectTool.prototype.selected3DtileByRay = function (graphic){
    const selected = []
    const rect = {
        maxLat: graphic.points[0].lat,
        maxLng: graphic.points[0].lng,
        minLat: graphic.points[0].lat,
        minLng: graphic.points[0].lng,
    };
    graphic.points.forEach((el) => {
        rect.maxLat = el.lat > rect.maxLat ? el.lat : rect.maxLat;
        rect.maxLng = el.lng > rect.maxLng ? el.lng : rect.maxLng;
        rect.minLat = el.lat < rect.minLat ? el.lat : rect.minLat;
        rect.minLng = el.lng < rect.minLng ? el.lng : rect.minLng;
    });
    // let latStep = (rect.maxLat - rect.minLat) / 20
    // let lngStep = (rect.maxLng - rect.minLng) / 20
    for (let lat = rect.minLat; lat < rect.maxLat; lat = lat + 0.0001) {
        for (let lng = rect.minLng; lng < rect.maxLng; lng = lng + 0.0001) {
            const lngLatPoint = new mars3d.LngLatPoint(lng, lat)
            if (graphic.isInPoly(lngLatPoint)) {
                const earthPosition = lngLatPoint.toCartesian()
                const ray = new Cesium.Ray(
                    Cesium.Cartesian3.multiplyByScalar(
                        earthPosition,
                        0.9,
                        new Cesium.Cartesian3()
                    ),
                    Cesium.Cartesian3.multiplyByScalar(
                        earthPosition,
                        1.1,
                        new Cesium.Cartesian3()
                    )
                )
                const pickedFeatures = viewer.scene.drillPickFromRay(ray)
                pickedFeatures.forEach(({
                    object
                }) => {
                    if (object &&
                        object instanceof Cesium.Cesium3DTileFeature &&
                        !selected.find(el => el === object)) {
                        selected.push(object)
                    }
                })
            }
        }
    }
    return selected
}

BQGIS_selectTool.prototype.format = function (feature) {
    if (feature instanceof Cesium.Cesium3DTileFeature) {
        if (this.shaderOption.highlight) {
            this.setHighlight(feature)
        }
        return this.setFeature(feature)
    } else if (feature.primitive && feature.primitive instanceof Cesium.Billboard) {
        if (this.shaderOption.highlight) {
            this.setHighlight(feature.primitive)
        }
        return this.setBillboard(feature)
    }
}

BQGIS_selectTool.prototype.setHighlight = function (object) {
    if(this.yellowEdge.selected.find(el => el === object)) return
    this.yellowEdge.selected.push(object)
}

BQGIS_selectTool.prototype.setBillboard = function (feature) {
    const billboardData = {
        type: 'billboard',
        data: {
            id: feature.id.id,
            name: feature.id.name,
        },
        feature
    }
    return billboardData
}

BQGIS_selectTool.prototype.setFeature = function (feature) {
    const keys = feature.getPropertyNames()
    const data = {}
    keys.forEach(key => {
        data[key] = feature.getProperty(key)
    })
    const tilesData = {
        type: '3DTile',
        data,
        feature
    }
    return tilesData
}

BQGIS_selectTool.prototype.clearShader  = function() {
    this.yellowEdge.selected = []
}

BQGIS_selectTool.prototype.clearRect  = function() {
    this.graphicLayer.clear()
}